package com.vhall.component.controller.pay;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.vhall.component.controller.pay.facade.AliPayFacade;
import com.vhall.component.entity.pay.entity.PaymentOrderEntity;
import com.vhall.component.framework.common.annotation.VhallComponent;
import com.vhall.component.framework.common.constant.CommonConstant;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.utils.RedisLocker;
import com.vhall.component.service.pay.PaymentOrderService;
import com.vhall.component.service.pay.PaymentService;
import com.vhall.component.service.pay.SettingService;
import com.vhall.component.service.pay.constant.Channel;
import com.vhall.component.service.pay.constant.OrderStatus;
import com.vhall.component.service.pay.constant.ServiceCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author xuyongzheng
 * @since 2020/10/22
 **/
@Slf4j
@VhallComponent
public class AliPayController implements AliPayFacade {

    @Value("${vhall.alipay.domain}")
    private String aliGateWay;
    @Autowired
    private PaymentOrderService paymentOrderService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private SettingService settingService;
    @Autowired
    private ThreadPoolTaskExecutor threadPool;

    @Override
    public String action(HttpServletRequest httpServletRequest) {
        Map<String, String> params = AliPayApi.toMap(httpServletRequest);
        log.info("支付宝回调:[{}]", JSON.toJSONString(params));
        params.remove("sign_type");
        // 支付宝订单号
        String tradNo = params.get("trade_no");
        // 我方订单号
        String bizNo = params.get("out_trade_no");
        if (StringUtils.isBlank(bizNo)) {
            return "fail";
        }
        RedisLocker.lock(bizNo, "1");
        try {
            PaymentOrderEntity order = paymentOrderService.findByBizOrderId(bizNo);
            if (order == null) {
                log.warn("回调对应订单不存在, biz_order_id:[{}]", bizNo);
                return "fail";
            }
            Map<String, String> setting = settingService.mapBy(order.getAppId(), Channel.ALIPAY, order.getServiceCode());
            if (null == setting || setting.size() == 0) {
                throw new BusinessException(BizErrorCode.COMP_PAY_PARAM_UNSET);
            }
            if (!checkSign(params, setting.get("rsaPublicKey"), setting.get("md5Key"))) {
                return "fail";
            }
            if (order.getStatus().equals(OrderStatus.SUCCESS)) {
                log.warn("订单已处理过, biz_order_id:[{}]", bizNo);
                return "success";
            }
            if (!"TRADE_SUCCESS".equals(params.get("trade_status"))) {
                return "fail";
            }
            if (ServiceCode.QR_PAY.equalsIgnoreCase(order.getServiceCode())
                    || ServiceCode.H5_PAY.equalsIgnoreCase(order.getServiceCode())) {
                // 主动查单, 确保收款成功
                AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
                Map<String, Object> q = new HashMap<>(4);
                q.put("trade_no", tradNo);
                q.put("out_trade_no", bizNo);
                request.setBizContent(JSON.toJSONString(q));
                AlipayClient client = buildClient(setting);
                AlipayTradeQueryResponse response = client.execute(request);
                if (!response.isSuccess() || !"TRADE_SUCCESS".equals(response.getTradeStatus())) {
                    return "fail";
                }
            }
            LocalDateTime ldt = LocalDateTimeUtil.parse(params.get("gmt_payment"),
                    DateTimeFormatter.ofPattern(CommonConstant.DATETIME_FORMAT));
            int effect = paymentOrderService.doPayed(bizNo, tradNo, Channel.ALIPAY, order.getServiceCode(), ldt);
            if (effect > 0) {
                // 进行组件支付接口的调用
                threadPool.execute(() -> paymentService.payNotify(order.getId()));
                return "success";
            }
        } catch (Exception ex) {
            log.error("支付宝回调处理异常", ex);
        } finally {
            RedisLocker.unlock(bizNo, "1");
        }
        return "fail";
    }

    private AlipayClient buildClient(Map<String, String> setting) {
        return AliPayApiConfig.builder()
                .setServiceUrl(aliGateWay)
                .setAppId(setting.get("appid"))
                .setAliPayPublicKey(setting.get("rsaPublicKey"))
                .setPrivateKey(setting.get("rsaPrivateKeyPKCS8"))
                .setSignType("RSA2")
                .setCharset(CommonConstant.UTF8)
                // 普通公钥方式
                .build()
                .getAliPayClient();
    }

    private boolean checkSign(Map<String, String> params, String rsaPublicKey, String md5Key) {
        try {
            if (StringUtils.isNotBlank(md5Key)) {
                String originSign = params.remove("sign");
                String[] keys = params.keySet().toArray(new String[0]);
                // 1. 参数名按照ASCII码表升序排序
                Arrays.sort(keys);
                // 2. 按照排序拼接参数名与参数值
                StringBuilder paramBuffer = new StringBuilder();
                for (String key : keys) {
                    paramBuffer.append(key).append("=").append(params.get(key) == null ? "" : params.get(key)).append("&");
                }
                String joint = paramBuffer.substring(0, paramBuffer.length() - 1);
                String buildSign = DigestUtils.md5Hex(joint + md5Key);
                return originSign.equalsIgnoreCase(buildSign);
            }
            return AlipaySignature.rsaCheckV2(params, rsaPublicKey, CommonConstant.UTF8, "RSA2");
        } catch (Exception ex) {
            log.error("支付宝通知参数, 验签失败:", ex);
            return false;
        }
    }


}
